package uk.co.accolade.clash.core;

import java.util.*;

public class UserChanges {

    private Username username;
    private Collection<ClashFilePath> changedFiles;

    public static UserChanges userChanges(Username username, ClashFilePath... filePaths) {
        return new UserChanges(username, Arrays.asList(filePaths));
    }

    public UserChanges( Username username, Collection<ClashFilePath> changedFiles ) {
        this.username = username;
        this.changedFiles = (changedFiles != null)? changedFiles : Collections.<ClashFilePath>emptyList();
        for( ClashFilePath filePath : this.changedFiles ) {
            assert filePath != null;
        }
    }

    public UserChanges findNewChanges( UserChanges otherChanges ) {
        return changesInFirstThatAreNotInSecond( otherChanges, this );
    }

    public UserChanges findRemovedChanges( UserChanges otherChanges ) {
        return changesInFirstThatAreNotInSecond( this, otherChanges );
    }

    public UserChanges intersect( UserChanges otherChanges, Username userForResults ) {
        List<ClashFilePath> intersectingChanges = new ArrayList<ClashFilePath>();
        for( ClashFilePath otherChange : otherChanges.changedFiles ) {
            if( changedFiles.contains( otherChange ) ) {
                intersectingChanges.add( otherChange );
            }
        }
        return new UserChanges( userForResults, intersectingChanges );
    }

    public Username getUsername() {
        return username;
    }

    public Collection<ClashFilePath> getChangedFilesReadOnly() {
        return Collections.unmodifiableCollection( changedFiles );
    }

    @Override
    public boolean equals( Object o ) {
        if( this == o ) return true;
        if( o == null || getClass() != o.getClass() ) return false;

        UserChanges that = (UserChanges) o;

        if( username != null ? !username.equals( that.username ) : that.username != null ) return false;
        UserChanges intersect = intersect(that, username );
        if(intersect.changedFiles.size() != changedFiles.size()
                || intersect.changedFiles.size() != that.changedFiles.size()) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result;
        result = ( username != null ? username.hashCode() : 0 );
        int filesHashCode = 0;
        for( ClashFilePath filePath : changedFiles ) {
            filesHashCode += filePath.hashCode();
        }
        result = 31 * result + filesHashCode;
        return result;
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder("<UserChange> user: [").append(username).append("] files: [");
        Iterator<ClashFilePath> files = changedFiles.iterator();
        while(files.hasNext()) {
            b.append( files.next() );
            if( files.hasNext() ) {
                b.append( ", " );
            }
        }
        b.append( "]" );
        return b.toString();
    }

    boolean contains( ClashFilePath filePath ) {
        return changedFiles.contains( filePath );
    }

    private UserChanges changesInFirstThatAreNotInSecond( UserChanges first, UserChanges second ) {
        validateUser( first, second );
        List<ClashFilePath> diff = new ArrayList<ClashFilePath>();
        for( ClashFilePath filePath : first.changedFiles ) {
            if( !second.changedFiles.contains( filePath ) ) {
                diff.add( filePath );
            }
        }
        return new UserChanges( username, diff );

    }

    private void validateUser( UserChanges one, UserChanges other ) {
        if( !one.username.equals( other.username ) ) {
            throw new IllegalArgumentException( "Can only compare changes for the same user" );
        }
    }

    public boolean hasChanges() {
        return !changedFiles.isEmpty();
    }
}